package com.lzh.service.impl;

import cn.hutool.json.JSONUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.lzh.config.loginConfig.GiteeConfigProperties;
import com.lzh.config.loginConfig.GithubConfigProperties;
import com.lzh.config.loginConfig.QqConfigProperties;
import com.lzh.domain.entity.User;
import com.lzh.domain.dto.WSChannelExtraDTO;
import com.lzh.domain.vo.UserVO;
import com.lzh.event.UserOnlineEvent;
import com.lzh.mapper.UserMapper;
import com.lzh.service.AuthLoginService;
import com.lzh.service.WebSocketService;
import com.lzh.service.adapter.WSAdapter;
import com.lzh.service.cache.UserCache;
import com.lzh.utils.JwtUtils;
import com.lzh.utils.RedisUtils;
import com.lzh.utils.ResponseResult;
import com.lzh.webSocket.NettyUtil;
import com.lzh.webSocket.domain.vo.req.WSReqUrlType;
import com.lzh.webSocket.domain.vo.resp.WSBaseResp;
import com.lzh.webSocket.domain.vo.ws.WSLoginSuccess;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.request.AuthGiteeRequest;
import me.zhyd.oauth.request.AuthGithubRequest;
import me.zhyd.oauth.request.AuthQqRequest;
import me.zhyd.oauth.request.AuthRequest;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@Component
public class WebSocketServiceImpl implements WebSocketService {

    private static final Duration EXPIRE_TIME = Duration.ofHours(1);

    private static final Cache<Integer, Channel> WAIT_LOGIN_CACHE = Caffeine.
            newBuilder()
            .expireAfterWrite(EXPIRE_TIME)
            .maximumSize(100)
            .build();

    private static final String LOGIN_CODE = "loginCode";

    /**
     * 所有已连接的websocket连接列表和一些额外参数
     */
    private static final ConcurrentHashMap<Channel, WSChannelExtraDTO> ONLINE_WS_MAP = new ConcurrentHashMap<>();
    /**
     * 所有在线的用户和对应的socket
     */
    private static final ConcurrentHashMap<Long, CopyOnWriteArrayList<Channel>> ONLINE_UID_MAP = new ConcurrentHashMap<>();

    @Autowired
    private UserMapper userMapper;

    public static ConcurrentHashMap<Channel, WSChannelExtraDTO> getOnlineMap() {
        return ONLINE_WS_MAP;
    }


    @Resource
    private GiteeConfigProperties giteeConfigProperties;

    @Resource
    private GithubConfigProperties githubConfigProperties;

    @Resource
    private QqConfigProperties qqConfigProperties;

    //公众号登录
    @Resource
    private WxMpService wxMpService;

    @Resource
    private JwtUtils jwtUtils;

    //第三方登录服务qq ，gitee， github
    @Resource
    private AuthLoginService authLoginService;

    @Resource
    private UserCache userCache;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    private Integer generateCode(Channel channel) {
        ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        try {
            Integer code;
            do {
                code = RedisUtils.integerInc(LOGIN_CODE, 1, TimeUnit.HOURS);
            } while (WAIT_LOGIN_CACHE.asMap().containsKey(code));

            WAIT_LOGIN_CACHE.put(code, channel);

            return code;
        } finally {
            reentrantLock.unlock();
        }
    }

    @SneakyThrows
    @Override
    public void handleLoginReq(Channel channel, WSReqUrlType data) {

        //    生成验证码
        Integer code = generateCode(channel);
        String url;
        if ("weixin".equals(data.getType())) {
            //生成二维码
            //第三方回调取出对应的code是否是网站生成的验证码
            WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, 600);
            url = wxMpQrCodeTicket.getUrl();
        } else {
            //生成第三方授权连接地址
            AuthRequest authRequest = getAuthRequest(data.getType());
            //类似于微信的openid用于第三方回调时候取出对应的code
            //url = authRequest.authorize(AuthStateUtils.createState());
            url = authRequest.authorize(code.toString());
        }

        //同一封装返回数据格式
        //使用websocket回应前端
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(WSAdapter.buildLoginResp(url))));
    }

    @Override
    public Boolean loginSuccess(Integer loginCode, Long uuid) {
        Channel channel = WAIT_LOGIN_CACHE.getIfPresent(loginCode);
        if (Objects.isNull(channel)) {
            return Boolean.FALSE;
        }


        User user = userMapper.selectById(uuid);

        //用户登录进行缓存token
        String token = authLoginService.userLogin(uuid);

        WAIT_LOGIN_CACHE.invalidate(loginCode);

        //用户登录 做下面几件事情
        //1更新上线列表
        //2返回给用户登录成功
        //3发送给对应的用户让好友知道上线成功
        //4发送用户上线事件
        loginSuccessHandle(channel, user, token);
        return Boolean.TRUE;
    }

    @Override
    public void testApplicationEventPublisher() {
        applicationEventPublisher.publishEvent(new UserOnlineEvent(this,new User()));
    }

    @Override
    public void connect(Channel channel) {
        ONLINE_WS_MAP.put(channel,new WSChannelExtraDTO());
    }

    @Override
    public void authorize(Channel channel, String token) {
        Boolean  verify =  authLoginService.verify(token);
        if(verify){
            ResponseResult<UserVO> userInfo = authLoginService.getUserInfo(token);
            User user = new User();
            BeanUtils.copyProperties(userInfo.getData(),user);
            loginSuccessHandle(channel,user,token);
        }else{
            sendMsg(channel, WSAdapter.againLogin());
        }
    }

    private void loginSuccessHandle(Channel channel, User user, String token) {
        //1更新上线列表
        online(channel, user.getId());
        //2返回给用户登录成功
        boolean hasPower = true;
        //3.相应给客户端登录成功并发送当前登录的用户信息
        sendMsg(channel, WSAdapter.buildLoginSuccessMessageResp(user, token, hasPower));
        //4发送登录信息
        boolean online = userCache.isOnline(user.getId());
        if(!online){
        //    发送登录事件
            user.setLastOptTime(new Date());
            user.setIpInfo(NettyUtil.getAttr(channel, NettyUtil.IP));
            applicationEventPublisher.publishEvent(new UserOnlineEvent(this,user));
        }

    }



    private void sendMsg(Channel channel, WSBaseResp<WSLoginSuccess> wsLoginSuccessWSBaseResp) {
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(wsLoginSuccessWSBaseResp)));
    }


    private void online(Channel channel, Long uuid) {
        getOrInitChannelExt(channel).setUid(uuid);
        ONLINE_UID_MAP.computeIfAbsent(uuid, uui -> new CopyOnWriteArrayList<>());
        ONLINE_UID_MAP.get(uuid).add(channel);
        NettyUtil.setAttr(channel, NettyUtil.UID, uuid);
    }


    private WSChannelExtraDTO getOrInitChannelExt(Channel channel) {
        //    获取channel隐射的额外参数
        WSChannelExtraDTO newWSChannelExtra = ONLINE_WS_MAP.getOrDefault(channel, new WSChannelExtraDTO());

        //获取老的额外参数，并且把新的放入其中
        WSChannelExtraDTO oldWSChannelExtra = ONLINE_WS_MAP
                .putIfAbsent(channel, newWSChannelExtra);

        return Objects.isNull(oldWSChannelExtra) ? newWSChannelExtra : oldWSChannelExtra;
    }


    private AuthRequest getAuthRequest(String source) {
        AuthRequest authRequest = null;
        switch (source) {
            case "gitee":
                authRequest = new AuthGiteeRequest(AuthConfig
                        .builder()
                        .clientId(giteeConfigProperties.getAppId())
                        .clientSecret(giteeConfigProperties.getAppSecret())
                        .redirectUri(giteeConfigProperties.getRedirectUrl())
                        .build()
                );
                break;
            case "github":
                authRequest = new AuthGithubRequest(AuthConfig
                        .builder()
                        .clientId(githubConfigProperties.getAppId())
                        .clientSecret(githubConfigProperties.getAppSecret())
                        .redirectUri(githubConfigProperties.getRedirectUrl())
                        .build()
                );
                break;
            case "qq":
                authRequest = new AuthQqRequest(AuthConfig
                        .builder()
                        .clientId(qqConfigProperties.getAppId())
                        .clientSecret(qqConfigProperties.getAppSecret())
                        .redirectUri(qqConfigProperties.getRedirectUrl())
                        .build()
                );
                break;
            default:
                break;
        }
        return authRequest;
    }
}
